#include "stdafx.h"
#include "PerlinNoise.h"

//-------------------------------------------------------------------------
// constructors
//-------------------------------------------------------------------------

CPerlinNoise::CPerlinNoise()
{
	m_bInitialized = false;
	GenerateLookupTables();
};

//-------------------------------------------------------------------------
// destructors
//-------------------------------------------------------------------------

CPerlinNoise::~CPerlinNoise()
{
	m_bInitialized = false;
};

//-------------------------------------------------------------------------
// member functions
//-------------------------------------------------------------------------

float CPerlinNoise::RandomPNFloat()
{
  return (float) ((rand() % (WRAP_INDEX + WRAP_INDEX)) - WRAP_INDEX) / WRAP_INDEX;
};

//-------------------------------------------------------------------------

void CPerlinNoise::ReSeed()
{
  srand((unsigned int) (time(NULL) + rand()));	// seed random number generator with system clock elapsed seconds
  GenerateLookupTables();
}

//-------------------------------------------------------------------------

float CPerlinNoise::SCurve(float t)
{
	return (t * t * (3.0f - 2.0f * t));	// component dropoff given by cubic approximation
}

//-------------------------------------------------------------------------

float CPerlinNoise::LinearInterpolation(float t, float a, float b)
{
	return (a + t * (b - a));
}

//-------------------------------------------------------------------------

void CPerlinNoise::ComputeDistanceToLatticePoints(float x, int &grid_point1, int &grid_point2, float &distance_to_gp1, float &distance_to_gp2)
{
	float t = x + MAX;
	grid_point1 = ((int)t) & MOD_MASK;
	grid_point2 = (grid_point1 + 1) & MOD_MASK;
	distance_to_gp1 = t - (int)t;
	distance_to_gp2 = distance_to_gp1 - 1.0f;
}

//-------------------------------------------------------------------------

void CPerlinNoise::GenerateLookupTables()
{

	// Fill permutation and gradient lookup tables with random values

	int i,j,k;

	for (i=0; i<WRAP_INDEX; i++)
	{

		// Fill permutation table

		m_iPermutation[i] = i;

		// Fill 1D gradient table

		m_fGradient1D[i] = RandomPNFloat();

		// Fill 2D gradient table

		m_fGradient2D[i].x = RandomPNFloat();
		m_fGradient2D[i].y = RandomPNFloat();
		m_fGradient2D[i].Normalize();

		// Fill 3D gradient table

		m_fGradient3D[i].x = RandomPNFloat();
		m_fGradient3D[i].y = RandomPNFloat();
		m_fGradient3D[i].z = RandomPNFloat();
		m_fGradient3D[i].Normalize();
	}

	// Shuffle permutation table entries so we can quickly index into the
	// gradient tables in a nonbiased way

	while (--i)
	{
		k = m_iPermutation[i];
		j = rand() % WRAP_INDEX;

		m_iPermutation[i] = m_iPermutation[j];
		m_iPermutation[j] = k;
	}

	// For added speed, avoid MOD operations and precompute m_iPermutation (P) 
	// to be twice as long, setting P[256...511] := P[0...255]

	for (i=0; i<WRAP_INDEX+2; i++)
	{

		// Finish permutation table

		m_iPermutation[WRAP_INDEX + i] = m_iPermutation[i];

		// Finish 1D gradient table

		m_fGradient1D[WRAP_INDEX + i] = m_fGradient1D[i];

		// Finish 2D gradient table

		m_fGradient2D[WRAP_INDEX + i].x = m_fGradient2D[i].x;
		m_fGradient2D[WRAP_INDEX + i].y = m_fGradient2D[i].y;

		// Finish 3D gradient table

		m_fGradient3D[WRAP_INDEX + i].x = m_fGradient3D[i].x;
		m_fGradient3D[WRAP_INDEX + i].y = m_fGradient3D[i].y;
		m_fGradient3D[WRAP_INDEX + i].z = m_fGradient3D[i].z;

	}

	m_bInitialized = true;
};

//-------------------------------------------------------------------------

float CPerlinNoise::PNoise1D(float x)
{

	// Make sure lookup tables have been generated

	if (!m_bInitialized) ReSeed();

	// Find grid points in lattice closest to given input value and 
	// compute distance from input value to those grid points

	int		grid_point1, grid_point2;
	float	distance_to_gp1, distance_to_gp2;

	ComputeDistanceToLatticePoints(x, grid_point1, grid_point2, distance_to_gp1, distance_to_gp2);

	// Exaggerate proximity of input to 0 or 1 using the Ease Curve

	float ease_curve_weight = SCurve(distance_to_gp1);

	// Compute gradient influences

	float gradient_influence1 = distance_to_gp1 * m_fGradient1D[m_iPermutation[grid_point1]];
	float gradient_influence2 = distance_to_gp2 * m_fGradient1D[m_iPermutation[grid_point2]];

	// Return the linear interpolation of the gradient influences

	return LinearInterpolation(ease_curve_weight, gradient_influence1, gradient_influence2);
}

//-------------------------------------------------------------------------

float CPerlinNoise::PNoise2D(vector2f vec2f)
{

	// Make sure lookup tables have been generated

	if (!m_bInitialized) ReSeed();

	// Find grid points in lattice closest to given input value and 
	// compute distance from input value to those grid points

	int		x_grid_point1, x_grid_point2, y_grid_point1, y_grid_point2;
	float	x_distance_to_gp1, x_distance_to_gp2, y_distance_to_gp1, y_distance_to_gp2;

	ComputeDistanceToLatticePoints(vec2f.x, x_grid_point1, x_grid_point2, x_distance_to_gp1, x_distance_to_gp2);
	ComputeDistanceToLatticePoints(vec2f.y, y_grid_point1, y_grid_point2, y_distance_to_gp1, y_distance_to_gp2);

	// Generate index locations for gradient table

	int index1, index2, corner1, corner2, corner3, corner4;

	index1 = m_iPermutation[x_grid_point1];
	index2 = m_iPermutation[x_grid_point2];

	corner1 = m_iPermutation[index1 + y_grid_point1];
	corner2 = m_iPermutation[index2 + y_grid_point1];
	corner3 = m_iPermutation[index1 + y_grid_point2];
	corner4 = m_iPermutation[index2 + y_grid_point2];

	// Exaggerate proximity of input to 0 or 1 using the Ease Curve

	float x_ease_curve_weight = SCurve(x_distance_to_gp1);
	float y_ease_curve_weight = SCurve(y_distance_to_gp1);

	// Compute gradient influences

	vector2f q;
	float a, b, u, v;

	q = m_fGradient2D[corner1]; u = x_distance_to_gp1 * q.x + y_distance_to_gp1 * q.y;
	q = m_fGradient2D[corner2]; v = x_distance_to_gp2 * q.x + y_distance_to_gp1 * q.y;
	a = LinearInterpolation(x_ease_curve_weight, u, v);

	q = m_fGradient2D[corner3]; u = x_distance_to_gp1 * q.x + y_distance_to_gp2 * q.y;
	q = m_fGradient2D[corner4]; v = x_distance_to_gp2 * q.x + y_distance_to_gp2 * q.y;
	b = LinearInterpolation(x_ease_curve_weight, u, v);

	// Return the linear interpolation of the gradient influences

	return LinearInterpolation(y_ease_curve_weight, a, b);
}

//-------------------------------------------------------------------------

float CPerlinNoise::PNoise3D(vector3f vec3f)
{
	// Make sure lookup tables have been generated

	if (!m_bInitialized) ReSeed();

	// Find grid points in lattice closest to given input value and 
	// compute distance from input value to those grid points

	int		x_grid_point1, x_grid_point2, y_grid_point1, y_grid_point2, z_grid_point1, z_grid_point2;
	float	x_distance_to_gp1, x_distance_to_gp2, y_distance_to_gp1, y_distance_to_gp2, z_distance_to_gp1, z_distance_to_gp2;

	ComputeDistanceToLatticePoints(vec3f.x, x_grid_point1, x_grid_point2, x_distance_to_gp1, x_distance_to_gp2);
	ComputeDistanceToLatticePoints(vec3f.y, y_grid_point1, y_grid_point2, y_distance_to_gp1, y_distance_to_gp2);
	ComputeDistanceToLatticePoints(vec3f.z, z_grid_point1, z_grid_point2, z_distance_to_gp1, z_distance_to_gp2);

	// Generate index locations for gradient table

	int index1, index2, corner1, corner2, corner3, corner4;

	index1 = m_iPermutation[x_grid_point1];
	index2 = m_iPermutation[x_grid_point2];

	corner1 = m_iPermutation[index1 + y_grid_point1];
	corner2 = m_iPermutation[index2 + y_grid_point1];
	corner3 = m_iPermutation[index1 + y_grid_point2];
	corner4 = m_iPermutation[index2 + y_grid_point2];

	// Exaggerate proximity of input to 0 or 1 using the Ease Curve

	float x_ease_curve_weight = SCurve(x_distance_to_gp1);
	float y_ease_curve_weight = SCurve(y_distance_to_gp1);
	float z_ease_curve_weight = SCurve(z_distance_to_gp1);

	// Compute gradient influences

	vector3f q;
	float a, b, u, v;

	q = m_fGradient3D[corner1 + z_grid_point1]; u = x_distance_to_gp1 * q.x + y_distance_to_gp1 * q.y + z_distance_to_gp1 * q.z;
	q = m_fGradient3D[corner2 + z_grid_point1]; v = x_distance_to_gp2 * q.x + y_distance_to_gp1 * q.y + z_distance_to_gp1 * q.z;
	a = LinearInterpolation(x_ease_curve_weight, u, v);

	q = m_fGradient3D[corner3 + z_grid_point1]; u = x_distance_to_gp1 * q.x + y_distance_to_gp2 * q.y + z_distance_to_gp1 * q.z;
	q = m_fGradient3D[corner4 + z_grid_point1]; v = x_distance_to_gp2 * q.x + y_distance_to_gp2 * q.y + z_distance_to_gp1 * q.z;
	b = LinearInterpolation(x_ease_curve_weight, u, v);

	float c = LinearInterpolation(y_ease_curve_weight, a, b);

	q = m_fGradient3D[corner1 + z_grid_point2]; u = x_distance_to_gp1 * q.x + y_distance_to_gp1 * q.y + z_distance_to_gp2 * q.z;
	q = m_fGradient3D[corner2 + z_grid_point2]; v = x_distance_to_gp2 * q.x + y_distance_to_gp1 * q.y + z_distance_to_gp2 * q.z;
	a = LinearInterpolation(x_ease_curve_weight, u, v);

	q = m_fGradient3D[corner3 + z_grid_point2]; u = x_distance_to_gp1 * q.x + y_distance_to_gp2 * q.y + z_distance_to_gp2 * q.z;
	q = m_fGradient3D[corner4 + z_grid_point2]; v = x_distance_to_gp2 * q.x + y_distance_to_gp2 * q.y + z_distance_to_gp2 * q.z;
	b = LinearInterpolation(x_ease_curve_weight, u, v);

	float d = LinearInterpolation(y_ease_curve_weight, a, b);

	return LinearInterpolation(z_ease_curve_weight, c, d);
}
